package com.treeman.api.security;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import org.apache.log4j.Logger;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;

import com.treeman.api.ApiRequestError.ApiError;
import com.treeman.api.security.context.AccessTokenAuthenticationToken;
import com.treeman.api.security.exception.InvalidAccessTokenException;
import com.treeman.user.User;
import com.treeman.user.localization.LocalizationManager;

public class ApiAuthenticationManager {

	private static Logger log = Logger
			.getLogger(ApiAuthenticationManager.class);

	private static ApiAuthenticationManager instance;

	private Map<String, User> tokenUserMap = new HashMap<String, User>();

	private Timer inActiveInvalidateTimer;

	private long activePeriod = PERIOD_10MIN;

	public final static long PERIOD_10MIN = 10 * 60 * 1000;
	public final static long PERIOD_10SEC = 10 * 1000;

	private ApiAuthenticationManager() {
		inActiveInvalidateTimer = new Timer();
		inActiveInvalidateTimer.scheduleAtFixedRate(
				new InvalidateInActiveTask(), new Date(), activePeriod);
	}

	public static ApiAuthenticationManager getInstance() {
		if (instance == null) {
			instance = new ApiAuthenticationManager();
		}
		return instance;
	}

	public synchronized Authentication authenticateUser(String token)
			throws InvalidAccessTokenException {
		if (token == null) {
			log.debug("Missing access token! Marking as ANONYMOUSE");
			return null;
		}

		if (tokenUserMap.containsKey(token)) {
			User user = tokenUserMap.get(token);
			log.debug("Access token found. Token: " + token + " user: "
					+ user.getUsername());

			if (!isActive(user.getLastActive())) {
				log.debug("Token is no longer active. Invalidating...");
				invalidateUserAccessToken(token, user.getUsername());
				throw new InvalidAccessTokenException(
						ApiError.INVALID_ACCESS_TOKEN,
						"Invalid access token. Token: " + token);
			}

			user.setLastActive(new Date());
			AccessTokenAuthenticationToken authentication = new AccessTokenAuthenticationToken(
					user, token, user.getAuthorities());

			tokenUserMap.put(token, user);
			return authentication;

		} else {
			log.debug("Invalid access token. Token: " + token);
			throw new InvalidAccessTokenException(
					ApiError.INVALID_ACCESS_TOKEN,
					"Invalid access token. Token: " + token);
		}

	}

	public synchronized void registerUserAccessToken(User user,
			String accessToken) {
		log.debug("Registering user access token: " + accessToken);
		user.setLastActive(new Date());
		tokenUserMap.put(accessToken, user);
	}

	public synchronized void invalidateUserAccessToken(String accessToken,
			String username) {
		log.debug("Invalidating user access token: " + accessToken);
		if (tokenUserMap.containsKey(accessToken)) {
			User userDetails = tokenUserMap.get(accessToken);
			if (userDetails.getUsername().equals(username) == false) {
				throw new AccessDeniedException(
						"Access token does not belongs to given username");
			}
			tokenUserMap.remove(accessToken);
			LocalizationManager.getInstance().removeUserLocalization(username);
		}
	}

	public static String generateToken() {
		UUID token = UUID.randomUUID();
		return token.toString();
	}

	public synchronized void invalidateInActiveTokens() {
		log.debug("Invalidate inActive tokens in progress...");
		if (tokenUserMap.size() > 0) {

			List<String> tokensToInvalidate = new ArrayList<String>();
			for (String token : tokenUserMap.keySet()) {
				User user = tokenUserMap.get(token);
				if (!isActive(user.getLastActive())) {
					log.debug("Token "
							+ token
							+ " is no longer active. Invalidating token and localization...");
					tokensToInvalidate.add(token);
					LocalizationManager.getInstance().removeUserLocalization(
							user.getUsername());
				}
			}

			for (String token : tokensToInvalidate) {
				tokenUserMap.remove(token);
			}
			log.debug("Invalidated tokens count: " + tokensToInvalidate.size());
		} else
			log.debug("No tokens to invalidate");
	}

	public Boolean isRegistered(String accessToken) {
		synchronized (tokenUserMap) {
			return tokenUserMap.containsKey(accessToken);
		}
	}

	private Boolean isActive(Date lastActive) {
		return !lastActive
				.before(new Date(new Date().getTime() - activePeriod));
	}
}

class InvalidateInActiveTask extends TimerTask {
	public InvalidateInActiveTask() {
	}

	@Override
	public void run() {
		ApiAuthenticationManager.getInstance().invalidateInActiveTokens();
	}
}
